<template>
  <div class="model-viewer">
    <div ref="viewerContainer" class="viewer-container">
      <video
        v-if="isVideoMode"
        ref="videoPlayer"
        class="video-player"
        :src="videoSrc"
        controls
        autoplay
        loop
        muted
        playsinline
      ></video>
    </div>
    <div v-if="isLoadingModel" class="loading-overlay">
      <div class="loading-spinner"></div>
      <p>正在加载3D模型...</p>
    </div>
  </div>
</template>

<script>
import * as THREE from 'three'
import { markRaw } from 'vue'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

export default {
  name: 'ModelViewer',
  props: {
    modelData: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      scene: null,
      camera: null,
      renderer: null,
      controls: null,
      modelLoaded: false,
      isLoadingModel: false,
      animationId: null,
      isVideoMode: false,
      videoSrc: ''
    }
  },
  mounted() {
    this.initThreeJS()
    if (this.modelData.url) {
      this.loadModel(this.modelData)
    }
  },
  beforeUnmount() {
    this.cleanup()
  },
  watch: {
    modelData(newData) {
      if (newData && newData.url) {
        this.loadModel(newData)
      }
    }
  },
  methods: {
    initThreeJS() {
      // 创建场景
      this.scene = markRaw(new THREE.Scene())
      this.scene.background = new THREE.Color(0xf0f0f0)

      // 创建相机
      this.camera = markRaw(new THREE.PerspectiveCamera(
        75,
        this.$refs.viewerContainer.clientWidth / this.$refs.viewerContainer.clientHeight,
        0.1,
        1000
      ))
      this.camera.position.set(5, 5, 5)

      // 创建渲染器
      this.renderer = markRaw(new THREE.WebGLRenderer({ antialias: true }))
      this.renderer.setSize(
        this.$refs.viewerContainer.clientWidth,
        this.$refs.viewerContainer.clientHeight
      )
      this.renderer.shadowMap.enabled = true
      this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
      this.$refs.viewerContainer.appendChild(this.renderer.domElement)

      // 添加轨道控制器
      this.controls = markRaw(new OrbitControls(this.camera, this.renderer.domElement))
      this.controls.enableDamping = true
      this.controls.dampingFactor = 0.05
      this.controls.enableZoom = true
      this.controls.enablePan = true

      // 添加光源
      this.addLights()

      // 开始渲染循环
      this.animate()

      // 监听窗口大小变化
      window.addEventListener('resize', this.onWindowResize)
    },

    addLights() {
      // 环境光
      const ambientLight = new THREE.AmbientLight(0x404040, 0.6)
      this.scene.add(ambientLight)

      // 主光源
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
      directionalLight.position.set(10, 10, 5)
      directionalLight.castShadow = true
      directionalLight.shadow.mapSize.width = 2048
      directionalLight.shadow.mapSize.height = 2048
      this.scene.add(directionalLight)

      // 补充光源
      const pointLight = new THREE.PointLight(0xffffff, 0.5)
      pointLight.position.set(-10, -10, -10)
      this.scene.add(pointLight)
    },

    async loadModel(modelData) {
      if (!modelData || !modelData.url) return
      this.modelLoaded = false
      this.isLoadingModel = true
      
      try {
        const { url, type } = modelData
        let model = null
        let result = null
        
        // 清除之前的模型
        this.clearScene()
        // 默认进入3D模式
        this.isVideoMode = false
        this.videoSrc = ''
        if (this.renderer && this.renderer.domElement) {
          this.renderer.domElement.style.display = ''
        }
        
        // 根据type选择对应的加载器（支持大写格式）
        switch (type.toUpperCase()) {
          case 'GLB':
          case 'GLTF': {
            console.log('🔄 正在加载GLB/GLTF模型...')
            const gltfLoader = new GLTFLoader()
            gltfLoader.setCrossOrigin('anonymous')
            const gltf = await gltfLoader.loadAsync(url)
            model = markRaw(gltf.scene)
            this.scene.add(model)
            result = gltf
            console.log('✅ 成功加载GLB/GLTF模型')
            break
          }
            
          case 'OBJ': {
            // 检查是否是zip文件
            if (url.toLowerCase().includes('.zip')) {
              // 尝试从ZIP文件中提取OBJ内容
              try {
                console.log('🔄 正在从ZIP文件中提取OBJ内容...')
                const response = await fetch(url)
                const zipData = await response.arrayBuffer()
                
                // 使用JSZip库解压ZIP文件
                const JSZip = (await import('jszip')).default
                const zip = await JSZip.loadAsync(zipData)
                
                // 查找OBJ文件
                let objFile = null
                for (const [filename, file] of Object.entries(zip.files)) {
                  if (filename.toLowerCase().endsWith('.obj')) {
                    objFile = file
                    break
                  }
                }
                
                if (!objFile) {
                  throw new Error('ZIP文件中未找到OBJ文件')
                }
                
                // 读取OBJ文件内容
                const objContent = await objFile.async('text')
                console.log('✅ 成功提取OBJ内容，长度:', objContent.length)
                
                // 创建Blob URL用于加载
                const objBlob = new Blob([objContent], { type: 'text/plain' })
                const objUrl = URL.createObjectURL(objBlob)
                
                // 加载OBJ模型
                const objLoader = new OBJLoader()
                const objModel = await objLoader.loadAsync(objUrl)
                
                // 清理临时URL
                URL.revokeObjectURL(objUrl)
                
                model = markRaw(objModel)
                this.scene.add(model)
                result = { scene: objModel }
                console.log('✅ 成功从ZIP文件中加载并显示OBJ模型')
              } catch (zipError) {
                console.error('❌ 从ZIP文件加载OBJ失败:', zipError)
                // 不抛出错误，而是显示友好的提示
                throw new Error('无法从ZIP文件中提取OBJ文件，请尝试下载文件后手动解压查看')
              }
            } else {
              // 直接加载OBJ文件
              console.log('🔄 正在加载OBJ文件...')
              const objLoader = new OBJLoader()
              objLoader.setCrossOrigin('anonymous')
              const objModel = await objLoader.loadAsync(url)
              model = markRaw(objModel)
              this.scene.add(model)
              result = { scene: objModel }
              console.log('✅ 成功加载OBJ模型')
            }
            break
          }
            
          case 'STL': {
            console.log('🔄 正在加载STL模型...')
            const stlLoader = new STLLoader()
            stlLoader.setCrossOrigin('anonymous')
            const stlGeometry = await stlLoader.loadAsync(url)
            const stlMaterial = new THREE.MeshPhongMaterial({ color: 0xaaaaaa })
            const stlMesh = new THREE.Mesh(stlGeometry, stlMaterial)
            model = markRaw(stlMesh)
            this.scene.add(model)
            result = { scene: stlMesh }
            console.log('✅ 成功加载STL模型')
            break
          }
            
          case 'FBX': {
            console.log('🔄 正在加载FBX模型...')
            const fbxLoader = new FBXLoader()
            fbxLoader.setCrossOrigin('anonymous')
            const fbxModel = await fbxLoader.loadAsync(url)
            model = markRaw(fbxModel)
            this.scene.add(model)
            result = { scene: fbxModel }
            console.log('✅ 成功加载FBX模型')
            break
          }
            
          case 'USDZ':
            // USDZ格式需要特殊处理，Three.js不直接支持
            throw new Error('USDZ格式暂不支持在3D查看器中显示。请下载文件查看。')
            
          case 'MP4': {
            // 切换到视频模式
            this.isVideoMode = true
            this.videoSrc = url
            if (this.renderer && this.renderer.domElement) {
              this.renderer.domElement.style.display = 'none'
            }
            result = { video: url }
            break
          }
            
          default:
            throw new Error(`不支持的文件格式: ${type}`)
        }
        
        // 调整相机位置以适应模型
        if (!this.isVideoMode && model) {
          this.fitCameraToModel(model)
        }
        
        this.modelLoaded = true
        this.$emit('model-loaded', result)
      } catch (error) {
        console.error('加载3D模型失败:', error)
        this.$emit('model-error', error)
      } finally {
        this.isLoadingModel = false
      }
    },

    clearScene() {
      // 清除场景中除了光源之外的所有对象
      const objectsToRemove = []
      this.scene.traverse((child) => {
        if (child !== this.scene && child.type !== 'Light' && child.type !== 'AmbientLight' && child.type !== 'DirectionalLight' && child.type !== 'PointLight') {
          objectsToRemove.push(child)
        }
      })
      objectsToRemove.forEach(obj => {
        if (obj.parent) {
          obj.parent.remove(obj)
        }
      })
    },

    fitCameraToModel(model) {
      const box = new THREE.Box3().setFromObject(model)
      const center = box.getCenter(new THREE.Vector3())
      const size = box.getSize(new THREE.Vector3())
      
      const maxDim = Math.max(size.x, size.y, size.z)
      const fov = this.camera.fov * (Math.PI / 180)
      let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2))
      cameraZ *= 1.5 // 添加一些边距
      
      this.camera.position.set(cameraZ, cameraZ, cameraZ)
      this.camera.lookAt(center)
      this.controls.target.copy(center)
      this.controls.update()
    },

    animate() {
      this.animationId = requestAnimationFrame(() => this.animate())
      this.controls.update()
      this.renderer.render(this.scene, this.camera)
    },

    onWindowResize() {
      if (!this.$refs.viewerContainer) return
      
      const width = this.$refs.viewerContainer.clientWidth
      const height = this.$refs.viewerContainer.clientHeight
      
      this.camera.aspect = width / height
      this.camera.updateProjectionMatrix()
      this.renderer.setSize(width, height)
    },

    resetCamera() {
      // 重置相机到默认位置
      this.camera.position.set(5, 5, 5)
      this.camera.lookAt(0, 0, 0)
      this.controls.target.set(0, 0, 0)
      this.controls.update()
    },

    cleanup() {
      if (this.animationId) {
        cancelAnimationFrame(this.animationId)
      }
      if (this.renderer) {
        this.renderer.dispose()
      }
      window.removeEventListener('resize', this.onWindowResize)
    }
  }
}
</script>

<style scoped>
.model-viewer {
  position: relative;
  width: 100%;
  height: 100%;
  background: #f0f0f0;
}

.viewer-container {
  width: 100%;
  height: 100%;
  position: relative;
}

.video-player {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #000;
  object-fit: contain;
}

.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(240, 240, 240, 0.9);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  z-index: 10;
}

.loading-spinner {
  width: 50px;
  height: 50px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 20px;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.loading-overlay p {
  color: #666;
  font-size: 16px;
  margin: 0;
}
</style>
